Match plasma & serum samples
import pandas as pd
samples = pd.read_csv('data/sample_sheet.csv')
# must be Healthy Control Study and must pass MiSeq QC
samples = samples.loc[(samples['Study'] == 'Healthy Controls') & (samples['MISEQ.QC.PASS'] == 'PASS')]
samples = samples.set_index('MT.Unique.ID').sort_values(by=['Participant.ID', 'Source'])
# capitalize & strip whitespace for consistency
for column in ['Gender', 'Race', 'Source']:
samples[column] = samples[column].str.capitalize()
samples[column] = samples[column].str.strip()
# use correct ontology terms
race_ontology = {'Asian': 'Asian',
'Black or african american': 'African_American',
'Mixed/asian & white': 'Multiracial',
'Mixed/asian &black': 'Multiracial',
'Mixed/black, white, asian': 'Multiracial',
'Native hawiian or other pacific islander': 'Pacific_Islander',
'Pacific islander': 'Pacific_Islander',
'White': 'White'}
for id in samples.index:
race = samples.at[id, 'Race']
samples.at[id, 'Race'] = race_ontology[race] if race in race_ontology else 'Multiracial'
# get matched plasma & serum samples
mir_counts = pd.read_csv("data/get_canonical/canon_mir_counts.csv", index_col=0)
samples.index = pd.Index(['X' + str(row) for row in samples.index])
serum_part_ids = set(samples.loc[samples['Source'] =='Serum']['Participant.ID'])
matched_samples = samples.loc[samples['Participant.ID'].isin(serum_part_ids)]
matched_mir_counts = mir_counts[matched_samples.index]
matched_samples.to_csv('data/matched_plasma-serum_samples.csv')
matched_mir_counts.to_csv('data/matched_plasma-serum_mir_counts.csv')
Load the sample data and miRNA counts
samples <- read.csv('data/matched_plasma-serum_samples.csv')
samples <- subset(samples, Library.Generation.Set != "SetRecheck" ) # exclude SetRecheck samples
# sample X11 is outlier -- remove it and its match
outlier_id <- samples[samples$X=="X11",]$Participant.ID
samples <- samples[samples$Participant.ID != outlier_id,]
counts <- read.csv('data/matched_plasma-serum_mir_counts.csv')
samples$Participant.ID <- factor(samples$Participant.ID) # ID is categorical, not numerical
samples$Sample.ID <- factor(samples$Sample.ID)
rownames(counts) = counts$X
rownames(samples) = samples$X
counts$X <- NULL # remove extra column
counts <- counts[,rownames(samples)]
head(samples)
head(counts)
Filter
library(edgeR)
design <- model.matrix(~Participant.ID + Source, samples)
dge = DGEList(counts = counts, samples = samples)
# require miRNAs to have CPM > 1 in at least 2 samples
countsPerMillion <- edgeR::cpm(dge)
countCheck <- countsPerMillion > 1
head(countCheck)
X1 X107 X2 X108 X14 X110 X15 X18 X21 X113 X22 X114 X26 X115 X40 X116 X46 X119 X48 X120 X52
hsa-let-7a-3p TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE FALSE TRUE TRUE TRUE FALSE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
hsa-let-7a-5p TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
hsa-let-7b-3p TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE FALSE TRUE TRUE TRUE
hsa-let-7b-5p TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
hsa-let-7c-3p FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
hsa-let-7c-5p TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
X121 X56 X122 X59 X123 X60 X124 X62 X125 X64 X126 X66 X127 X67 X128 X68 X129 X69 X130 X131 X77
hsa-let-7a-3p TRUE TRUE FALSE TRUE FALSE TRUE FALSE TRUE FALSE TRUE TRUE TRUE TRUE TRUE FALSE TRUE FALSE TRUE FALSE TRUE TRUE
hsa-let-7a-5p TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
hsa-let-7b-3p TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
hsa-let-7b-5p TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
hsa-let-7c-3p FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
hsa-let-7c-5p TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
X132 X133 X88 X134 X89 X135 X90 X136 X93 X137 X138
hsa-let-7a-3p FALSE FALSE TRUE TRUE TRUE FALSE TRUE TRUE TRUE TRUE TRUE
hsa-let-7a-5p TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
hsa-let-7b-3p TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
hsa-let-7b-5p TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
hsa-let-7c-3p FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE
hsa-let-7c-5p TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
keep <- which(rowSums(countCheck) >= 2)
dge <- dge[keep,]
Explore variance
library(SingleCellExperiment)
library(scater)
reads_sce <- SingleCellExperiment(assays=list(counts=dge$counts), colData=dge$samples)
# remove unexpressed miRNAs
keep_feature <- rowSums(counts(reads_sce) > 0) > 0
reads_sce <- reads_sce[keep_feature, ]
reads_sce <- calculateQCMetrics(reads_sce)
Note that the names of some metrics have changed, see 'Renamed metrics' in ?calculateQCMetrics.
Old names are currently maintained for back-compatibility, but may be removed in future releases.
# create separate plasma & serum SCEs
plasma_samples <- subset(colData(reads_sce), Source == "Plasma")
plasma_counts <- as.data.frame(counts(reads_sce))[rownames(plasma_samples)]
plasma_sce <- SingleCellExperiment(assays=list(counts=as.matrix(plasma_counts)), colData=plasma_samples)
plasma_sce <- calculateQCMetrics(plasma_sce)
Note that the names of some metrics have changed, see 'Renamed metrics' in ?calculateQCMetrics.
Old names are currently maintained for back-compatibility, but may be removed in future releases.
serum_samples <- subset(colData(reads_sce), Source == "Serum")
serum_counts <- as.data.frame(counts(reads_sce))[rownames(serum_samples)]
serum_sce <- SingleCellExperiment(assays=list(counts=as.matrix(serum_counts)), colData=serum_samples)
serum_sce <- calculateQCMetrics(serum_sce)
Note that the names of some metrics have changed, see 'Renamed metrics' in ?calculateQCMetrics.
Old names are currently maintained for back-compatibility, but may be removed in future releases.
# log transform
cpm(reads_sce) <- calculateCPM(reads_sce)
reads_sce <- normalize(reads_sce)
using library sizes as size factors
logcounts(reads_sce) <- log2(calculateCPM(reads_sce) + 1)
Examine sources of variation
plotPCA(reads_sce, exprs_values = "logcounts", colour_by = "Library.Generation.Set", size_by = "total_features", shape_by = "Source")
non-plotting arguments like 'exprs_values' should go in 'run_args'

for (var in c("total_features", "Age", "Library.Generation.Set", "Index", "Participant.ID", "Source", "Race", "Gender", "Collection.Date")) {
print(
plotQC(reads_sce, type = "find-pcs", exprs_values = "logcounts", variable = var)
) + ggtitle(var)
}









Normalize & Remove unwanted sources of variation
library(RUVSeq)
library(ggplot2)
library(mvoutlier)
Loading required package: sgeostat
sROC 0.1-2 loaded
dge <- calcNormFactors(dge)
dge <- estimateGLMCommonDisp(dge, design)
dge <- estimateGLMTagwiseDisp(dge, design)
fit <- glmFit(dge, design)
res <- residuals(fit, type="deviance")
set <- newSeqExpressionSet(dge$counts, phenoData=dge$samples)
ruvr_sets <- list()
for(k in 1:5) {
ruvr_sets[[k]] <- RUVr(set, row.names(dge), k=k, res)
assay(reads_sce, paste("RUVr k=", toString(k))) <- log2(t(t(assayData(ruvr_sets[[k]])$normalizedCounts) / colSums(assayData(ruvr_sets[[k]])$normalizedCounts) * 1e6) + 1)
}
for(n in assayNames(reads_sce)) {
print(
plotPCA(
reads_sce,
colour_by = "Library.Generation.Set",
size_by = "total_features",
shape_by = "Source",
exprs_values = n
) + ggtitle(n)
)
}
non-plotting arguments like 'exprs_values' should go in 'run_args'








for(k in 1:5) {
ruvr_sets[[k]]$set <- RUVr(set, row.names(dge), k=k, res)
assay(reads_sce, paste("RUVr k=", toString(k))) <- log2(t(t(assayData(ruvr_sets[[k]]$set)$normalizedCounts) / colSums(assayData(ruvr_sets[[k]]$set)$normalizedCounts) * 1e6) + 1)
}
Error in `[[<-.data.frame`(`*tmp*`, i, value = new("SeqExpressionSet", :
replacement has 989 rows, data has 53
Detect outliers
reads_sce <- runPCA(reads_sce, use_coldata = TRUE, detect_outliers = TRUE)
failed to find 'pct_counts_feature_control' in column metadatafailed to find 'total_features_by_counts_feature_control' in column metadatafailed to find 'log10_total_counts_endogenous' in column metadatafailed to find 'log10_total_counts_feature_control' in column metadata
outliers <- colnames(reads_sce)[reads_sce$outlier]
head(outliers)
character(0)
Examine sources of variance after removing unwanted variation
for (var in c("total_features", "Age", "Library.Generation.Set", "Index", "Participant.ID", "Source", "Race", "Gender", "Collection.Date")) {
print(
plotQC(reads_sce, type = "find-pcs", exprs_values = "RUVr1", variable = var)
) + ggtitle(var)
}
Error in assay(object, exprs_values) :
'assay(<SingleCellExperiment>, i="character", ...)' invalid subscript 'i'
'RUVr1' not in names(assays(<SingleCellExperiment>))
for (var in c("total_features", "Age", "Library.Generation.Set", "Index", "Participant.ID", "Source", "Race", "Gender", "Collection.Date")) {
print(
plotQC(reads_sce, type = "find-pcs", exprs_values = "RUVr k= 2", variable = var)
) + ggtitle(var)
}









Visualize top highly-expressed plasma-vs-serum miRNAs
library(RColorBrewer)
library(reshape2)
# Ryan's code, modified
norm.expr.matr <- exprs(reads_sce)
# Rank the mean expression values for plasma/serum miRs. Highest expression = 1
mean.expr.plasma.rank <- rank(-1*rowMeans(norm.expr.matr[, colData(reads_sce)$Source=="Plasma"]))
mean.expr.serum.rank <- rank(-1*rowMeans(norm.expr.matr[, colData(reads_sce)$Source=="Serum"]))
top_N <- 18 # top_N=18 resuls in 20 miRNAs in the plot
top.miRs <- row.names(norm.expr.matr)[mean.expr.plasma.rank <= top_N | mean.expr.serum.rank <= top_N] # get the names of the top miRs in plasma or serum
norm.expr.top <- norm.expr.matr[top.miRs, ] # Get the expression matrix for the top mIRs
norm.expr.melt <- reshape2::melt(norm.expr.top) # Convert your normalized expression matrix to a 3 column data.frame (row.name, col.name, expression value). Melt is in the dpylr package, I believe.
colnames(norm.expr.melt) <- c("miR.ID", "MT.Unique.ID", "norm.expr") # just so it's easier for me to tell you which columns I'm using.
norm.expr.melt$Source <- ""
for (row_num in 1:nrow(norm.expr.melt)){ # Pull the plasma/serum source value from the column metadata.
mt_unique_id <- norm.expr.melt[row_num, ]$MT.Unique.ID
norm.expr.melt[row_num, "Source"] <- as.character(samples[mt_unique_id,"Source"])
}
# This would be a simple boxplot with plasma/serum side-by-side. Overlaying the boxes over points takes a little more tweaking to get the dodge/width right, but it's doable.
ggplot(norm.expr.melt, aes(x=reorder(miR.ID, norm.expr, FUN=median), y=norm.expr, fill=Source)) + geom_boxplot(pos="dodge", outlier.size=0.5) +
ggtitle("Top 20 Expressed miRNAs") + ylab("Normalized Expression") + xlab("miRNA ID") +
theme(panel.grid.major.x = element_line(size = 0.5, linetype = 'solid', colour = "grey"),
panel.grid.major.y = element_blank(),
axis.text.x = element_text(angle = 90, hjust = 1))

ggplot(norm.expr.melt, aes(x=reorder(miR.ID, norm.expr, FUN=median), y=norm.expr, fill=Source)) + geom_boxplot(pos="dodge", outlier.size=0.5) +
ggtitle("Top 20 Expressed miRNAs") + ylab("Normalized Expression") + xlab("miRNA ID") +
theme(panel.grid.major.y = element_line(size = 0.5, linetype = 'solid', colour = "grey"),
panel.grid.major.x = element_blank()) + coord_flip()

DE Analysis with DESeq2
library(DESeq2) # TODO: use filtered & RUVSeq-corrected data
deseq2Data <- DESeqDataSetFromMatrix(countData = dge$counts, design = ~ Participant.ID + Source, colData = dge$samples)
deseq2Data <- DESeq(deseq2Data)
estimating size factors
estimating dispersions
gene-wise dispersion estimates
mean-dispersion relationship
final dispersion estimates
fitting model and testing
results <- results(deseq2Data, cooksCutoff=FALSE, independentFiltering=FALSE)
head(as.data.frame(results))
Create a heatmap of sample-to-sample distances
sampleDists <- dist(t(assay(vstData))) # compute sample-to-sample distances
sampleDistMatrix <- as.matrix(sampleDists)
rownames(sampleDistMatrix) <- paste(vstData$Participant.ID, vstData$Source, sep=" - ")
colnames(sampleDistMatrix) <- NULL
colors <- colorRampPalette( rev(brewer.pal(9, "Blues")) )(255)
pheatmap(sampleDistMatrix,
clustering_distance_rows=sampleDists,
clustering_distance_cols=sampleDists,
col=colors)
Create a PCA plot showing Age x Gender
plotPCA(
reads_sce,
colour_by = "Age",
shape_by = "Gender",
size_by = "total_features",
exprs_values = "RUVr k= 2"
) + ggtitle("RUVSeq-Normalized Expression (k=2)")
non-plotting arguments like 'exprs_values' should go in 'run_args'

LS0tCnRpdGxlOiAibWlSTkEgRGlmZmVyZW50aWFsIGV4cHJlc3Npb24gYW5hbHlzaXMgZm9yIG1hdGNoZWQgcGxhc21hICYgc2VydW0gc2FtcGxlcyIKb3V0cHV0OiBodG1sX25vdGVib29rCmVkaXRvcl9vcHRpb25zOiAKICBjaHVua19vdXRwdXRfdHlwZTogaW5saW5lCi0tLQojIE1hdGNoIHBsYXNtYSAmIHNlcnVtIHNhbXBsZXMKYGBge3B5dGhvbjN9CmltcG9ydCBwYW5kYXMgYXMgcGQKc2FtcGxlcyA9IHBkLnJlYWRfY3N2KCdkYXRhL3NhbXBsZV9zaGVldC5jc3YnKQojIG11c3QgYmUgSGVhbHRoeSBDb250cm9sIFN0dWR5IGFuZCBtdXN0IHBhc3MgTWlTZXEgUUMKc2FtcGxlcyA9IHNhbXBsZXMubG9jWyhzYW1wbGVzWydTdHVkeSddID09ICdIZWFsdGh5IENvbnRyb2xzJykgJiAoc2FtcGxlc1snTUlTRVEuUUMuUEFTUyddID09ICdQQVNTJyldICAKc2FtcGxlcyA9IHNhbXBsZXMuc2V0X2luZGV4KCdNVC5VbmlxdWUuSUQnKS5zb3J0X3ZhbHVlcyhieT1bJ1BhcnRpY2lwYW50LklEJywgJ1NvdXJjZSddKQojIGNhcGl0YWxpemUgJiBzdHJpcCB3aGl0ZXNwYWNlIGZvciBjb25zaXN0ZW5jeQpmb3IgY29sdW1uIGluIFsnR2VuZGVyJywgJ1JhY2UnLCAnU291cmNlJ106CiAgICBzYW1wbGVzW2NvbHVtbl0gPSBzYW1wbGVzW2NvbHVtbl0uc3RyLmNhcGl0YWxpemUoKQogICAgc2FtcGxlc1tjb2x1bW5dID0gc2FtcGxlc1tjb2x1bW5dLnN0ci5zdHJpcCgpCiMgdXNlIGNvcnJlY3Qgb250b2xvZ3kgdGVybXMKcmFjZV9vbnRvbG9neSA9IHsnQXNpYW4nOiAnQXNpYW4nLAogJ0JsYWNrIG9yIGFmcmljYW4gYW1lcmljYW4nOiAnQWZyaWNhbl9BbWVyaWNhbicsCiAnTWl4ZWQvYXNpYW4gJiB3aGl0ZSc6ICdNdWx0aXJhY2lhbCcsCiAnTWl4ZWQvYXNpYW4gJmJsYWNrJzogJ011bHRpcmFjaWFsJywKICdNaXhlZC9ibGFjaywgd2hpdGUsIGFzaWFuJzogJ011bHRpcmFjaWFsJywKICdOYXRpdmUgaGF3aWlhbiBvciBvdGhlciBwYWNpZmljIGlzbGFuZGVyJzogJ1BhY2lmaWNfSXNsYW5kZXInLAogJ1BhY2lmaWMgaXNsYW5kZXInOiAnUGFjaWZpY19Jc2xhbmRlcicsCiAnV2hpdGUnOiAnV2hpdGUnfQpmb3IgaWQgaW4gc2FtcGxlcy5pbmRleDoKICAgIHJhY2UgPSBzYW1wbGVzLmF0W2lkLCAnUmFjZSddCiAgICBzYW1wbGVzLmF0W2lkLCAnUmFjZSddID0gcmFjZV9vbnRvbG9neVtyYWNlXSBpZiByYWNlIGluIHJhY2Vfb250b2xvZ3kgZWxzZSAnTXVsdGlyYWNpYWwnCiMgZ2V0IG1hdGNoZWQgcGxhc21hICYgc2VydW0gc2FtcGxlcwptaXJfY291bnRzID0gcGQucmVhZF9jc3YoImRhdGEvZ2V0X2Nhbm9uaWNhbC9jYW5vbl9taXJfY291bnRzLmNzdiIsIGluZGV4X2NvbD0wKQpzYW1wbGVzLmluZGV4ID0gcGQuSW5kZXgoWydYJyArIHN0cihyb3cpIGZvciByb3cgaW4gc2FtcGxlcy5pbmRleF0pCnNlcnVtX3BhcnRfaWRzID0gc2V0KHNhbXBsZXMubG9jW3NhbXBsZXNbJ1NvdXJjZSddID09J1NlcnVtJ11bJ1BhcnRpY2lwYW50LklEJ10pCm1hdGNoZWRfc2FtcGxlcyA9IHNhbXBsZXMubG9jW3NhbXBsZXNbJ1BhcnRpY2lwYW50LklEJ10uaXNpbihzZXJ1bV9wYXJ0X2lkcyldCm1hdGNoZWRfbWlyX2NvdW50cyA9IG1pcl9jb3VudHNbbWF0Y2hlZF9zYW1wbGVzLmluZGV4XQptYXRjaGVkX3NhbXBsZXMudG9fY3N2KCdkYXRhL21hdGNoZWRfcGxhc21hLXNlcnVtX3NhbXBsZXMuY3N2JykKbWF0Y2hlZF9taXJfY291bnRzLnRvX2NzdignZGF0YS9tYXRjaGVkX3BsYXNtYS1zZXJ1bV9taXJfY291bnRzLmNzdicpCmBgYAojIyBMb2FkIHRoZSBzYW1wbGUgZGF0YSBhbmQgbWlSTkEgY291bnRzCmBgYHtyfQpzYW1wbGVzIDwtIHJlYWQuY3N2KCdkYXRhL21hdGNoZWRfcGxhc21hLXNlcnVtX3NhbXBsZXMuY3N2JykKc2FtcGxlcyA8LSBzdWJzZXQoc2FtcGxlcywgTGlicmFyeS5HZW5lcmF0aW9uLlNldCAhPSAiU2V0UmVjaGVjayIgKSAgIyBleGNsdWRlIFNldFJlY2hlY2sgc2FtcGxlcwojIHNhbXBsZSBYMTEgaXMgb3V0bGllciAtLSByZW1vdmUgaXQgYW5kIGl0cyBtYXRjaApvdXRsaWVyX2lkIDwtIHNhbXBsZXNbc2FtcGxlcyRYPT0iWDExIixdJFBhcnRpY2lwYW50LklECnNhbXBsZXMgPC0gc2FtcGxlc1tzYW1wbGVzJFBhcnRpY2lwYW50LklEICE9IG91dGxpZXJfaWQsXQpjb3VudHMgPC0gcmVhZC5jc3YoJ2RhdGEvbWF0Y2hlZF9wbGFzbWEtc2VydW1fbWlyX2NvdW50cy5jc3YnKQpzYW1wbGVzJFBhcnRpY2lwYW50LklEIDwtIGZhY3RvcihzYW1wbGVzJFBhcnRpY2lwYW50LklEKSAgIyBJRCBpcyBjYXRlZ29yaWNhbCwgbm90IG51bWVyaWNhbApzYW1wbGVzJFNhbXBsZS5JRCA8LSBmYWN0b3Ioc2FtcGxlcyRTYW1wbGUuSUQpCnJvd25hbWVzKGNvdW50cykgPSBjb3VudHMkWApyb3duYW1lcyhzYW1wbGVzKSA9IHNhbXBsZXMkWApjb3VudHMkWCA8LSBOVUxMICAjIHJlbW92ZSBleHRyYSBjb2x1bW4KY291bnRzIDwtIGNvdW50c1sscm93bmFtZXMoc2FtcGxlcyldCmhlYWQoc2FtcGxlcykKaGVhZChjb3VudHMpCmBgYAojIyBGaWx0ZXIKYGBge3J9CmxpYnJhcnkoZWRnZVIpCmRlc2lnbiA8LSBtb2RlbC5tYXRyaXgoflBhcnRpY2lwYW50LklEICsgU291cmNlLCBzYW1wbGVzKQpkZ2UgPSBER0VMaXN0KGNvdW50cyA9IGNvdW50cywgc2FtcGxlcyA9IHNhbXBsZXMpCiMgcmVxdWlyZSBtaVJOQXMgdG8gaGF2ZSBDUE0gPiAxIGluIGF0IGxlYXN0IDIgc2FtcGxlcwpjb3VudHNQZXJNaWxsaW9uIDwtIGVkZ2VSOjpjcG0oZGdlKQpjb3VudENoZWNrIDwtIGNvdW50c1Blck1pbGxpb24gPiAxCmhlYWQoY291bnRDaGVjaykKa2VlcCA8LSB3aGljaChyb3dTdW1zKGNvdW50Q2hlY2spID49IDIpIApkZ2UgPC0gZGdlW2tlZXAsXQpgYGAKIyMgRXhwbG9yZSB2YXJpYW5jZQpgYGB7cn0KbGlicmFyeShTaW5nbGVDZWxsRXhwZXJpbWVudCkKbGlicmFyeShzY2F0ZXIpCnJlYWRzX3NjZSA8LSBTaW5nbGVDZWxsRXhwZXJpbWVudChhc3NheXM9bGlzdChjb3VudHM9ZGdlJGNvdW50cyksICBjb2xEYXRhPWRnZSRzYW1wbGVzKQojIHJlbW92ZSB1bmV4cHJlc3NlZCBtaVJOQXMKa2VlcF9mZWF0dXJlIDwtIHJvd1N1bXMoY291bnRzKHJlYWRzX3NjZSkgPiAwKSA+IDAKcmVhZHNfc2NlIDwtIHJlYWRzX3NjZVtrZWVwX2ZlYXR1cmUsIF0KcmVhZHNfc2NlIDwtIGNhbGN1bGF0ZVFDTWV0cmljcyhyZWFkc19zY2UpCiMgY3JlYXRlIHNlcGFyYXRlIHBsYXNtYSAmIHNlcnVtIFNDRXMKcGxhc21hX3NhbXBsZXMgPC0gc3Vic2V0KGNvbERhdGEocmVhZHNfc2NlKSwgU291cmNlID09ICJQbGFzbWEiKQpwbGFzbWFfY291bnRzIDwtIGFzLmRhdGEuZnJhbWUoY291bnRzKHJlYWRzX3NjZSkpW3Jvd25hbWVzKHBsYXNtYV9zYW1wbGVzKV0KcGxhc21hX3NjZSA8LSBTaW5nbGVDZWxsRXhwZXJpbWVudChhc3NheXM9bGlzdChjb3VudHM9YXMubWF0cml4KHBsYXNtYV9jb3VudHMpKSwgY29sRGF0YT1wbGFzbWFfc2FtcGxlcykKcGxhc21hX3NjZSA8LSBjYWxjdWxhdGVRQ01ldHJpY3MocGxhc21hX3NjZSkKc2VydW1fc2FtcGxlcyA8LSBzdWJzZXQoY29sRGF0YShyZWFkc19zY2UpLCBTb3VyY2UgPT0gIlNlcnVtIikKc2VydW1fY291bnRzIDwtIGFzLmRhdGEuZnJhbWUoY291bnRzKHJlYWRzX3NjZSkpW3Jvd25hbWVzKHNlcnVtX3NhbXBsZXMpXQpzZXJ1bV9zY2UgPC0gU2luZ2xlQ2VsbEV4cGVyaW1lbnQoYXNzYXlzPWxpc3QoY291bnRzPWFzLm1hdHJpeChzZXJ1bV9jb3VudHMpKSwgY29sRGF0YT1zZXJ1bV9zYW1wbGVzKQpzZXJ1bV9zY2UgPC0gY2FsY3VsYXRlUUNNZXRyaWNzKHNlcnVtX3NjZSkKIyBsb2cgdHJhbnNmb3JtCmNwbShyZWFkc19zY2UpIDwtIGNhbGN1bGF0ZUNQTShyZWFkc19zY2UpCnJlYWRzX3NjZSA8LSBub3JtYWxpemUocmVhZHNfc2NlKQpsb2djb3VudHMocmVhZHNfc2NlKSA8LSBsb2cyKGNhbGN1bGF0ZUNQTShyZWFkc19zY2UpICsgMSkKIyB2aXN1YWxpemUKaGlzdChyZWFkc19zY2UkdG90YWxfY291bnRzLCBicmVha3M9MTAwKSAgIyBjb3VudHMgcGVyIHNhbXBsZQpoaXN0KHJlYWRzX3NjZSR0b3RhbF9mZWF0dXJlcywgYnJlYWtzPTEwMCkgICMgY291bnRzIHBlciBtaVJOQQpwbG90UUMocmVhZHNfc2NlLCB0eXBlID0gImhpZ2hlc3QtZXhwcmVzc2lvbiIpICsgZ2d0aXRsZSgiUGxhc21hICYgU2VydW0iKQpwbG90UUMocGxhc21hX3NjZSwgdHlwZSA9ICJoaWdoZXN0LWV4cHJlc3Npb24iKSArIGdndGl0bGUoIlBsYXNtYSIpCnBsb3RRQyhzZXJ1bV9zY2UsIHR5cGUgPSAiaGlnaGVzdC1leHByZXNzaW9uIikgKyBnZ3RpdGxlKCJTZXJ1bSIpCnBsb3RRQyhyZWFkc19zY2UsIHR5cGU9ImV4cGxhbmF0b3J5LXZhcmlhYmxlcyIsIHZhcmlhYmxlcz1jKCJJbmRleCIsICJQYXJ0aWNpcGFudC5JRCIsICJDb2xsZWN0aW9uLkRhdGUiLCAiTGlicmFyeS5HZW5lcmF0aW9uLlNldCIsICJNaVNlcS5RQy5SdW4iLCAiU291cmNlIiwgJ3RvdGFsX2ZlYXR1cmVzJywgIkFnZSIsICJSYWNlIiwgIlNleCIpKQpgYGAKIyMgRXhhbWluZSBzb3VyY2VzIG9mIHZhcmlhdGlvbgpgYGB7cn0KcGxvdFBDQShyZWFkc19zY2UsIGV4cHJzX3ZhbHVlcyA9ICJsb2djb3VudHMiLCBjb2xvdXJfYnkgPSAiTGlicmFyeS5HZW5lcmF0aW9uLlNldCIsIHNpemVfYnkgPSAidG90YWxfZmVhdHVyZXMiLCBzaGFwZV9ieSA9ICJTb3VyY2UiKQpmb3IgKHZhciBpbiBjKCJ0b3RhbF9mZWF0dXJlcyIsICJBZ2UiLCAiTGlicmFyeS5HZW5lcmF0aW9uLlNldCIsICJJbmRleCIsICJQYXJ0aWNpcGFudC5JRCIsICJTb3VyY2UiLCAiUmFjZSIsICJHZW5kZXIiLCAiQ29sbGVjdGlvbi5EYXRlIikpIHsKICBwcmludCgKICAgIHBsb3RRQyhyZWFkc19zY2UsIHR5cGUgPSAiZmluZC1wY3MiLCBleHByc192YWx1ZXMgPSAibG9nY291bnRzIiwgdmFyaWFibGUgPSB2YXIpCiAgICApICArIGdndGl0bGUodmFyKSAKICB9CmBgYAoKIyMgTm9ybWFsaXplICYgUmVtb3ZlIHVud2FudGVkIHNvdXJjZXMgb2YgdmFyaWF0aW9uCmBgYHtyfQpsaWJyYXJ5KFJVVlNlcSkKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KG12b3V0bGllcikKZGdlIDwtIGNhbGNOb3JtRmFjdG9ycyhkZ2UpCmRnZSA8LSBlc3RpbWF0ZUdMTUNvbW1vbkRpc3AoZGdlLCBkZXNpZ24pCmRnZSA8LSBlc3RpbWF0ZUdMTVRhZ3dpc2VEaXNwKGRnZSwgZGVzaWduKQpmaXQgPC0gZ2xtRml0KGRnZSwgZGVzaWduKQpyZXMgPC0gcmVzaWR1YWxzKGZpdCwgdHlwZT0iZGV2aWFuY2UiKQpzZXQgPC0gbmV3U2VxRXhwcmVzc2lvblNldChkZ2UkY291bnRzLCBwaGVub0RhdGE9ZGdlJHNhbXBsZXMpCnJ1dnJfc2V0cyA8LSBsaXN0KCkKZm9yKGsgaW4gMTo1KSB7CiAgcnV2cl9zZXRzW1trXV0gPC0gUlVWcihzZXQsIHJvdy5uYW1lcyhkZ2UpLCBrPWssIHJlcykKICBhc3NheShyZWFkc19zY2UsIHBhc3RlKCJSVVZyIGs9IiwgdG9TdHJpbmcoaykpKSA8LSBsb2cyKHQodChhc3NheURhdGEocnV2cl9zZXRzW1trXV0pJG5vcm1hbGl6ZWRDb3VudHMpIC8gY29sU3Vtcyhhc3NheURhdGEocnV2cl9zZXRzW1trXV0pJG5vcm1hbGl6ZWRDb3VudHMpICogMWU2KSArIDEpCn0KZm9yKG4gaW4gYXNzYXlOYW1lcyhyZWFkc19zY2UpKSB7CiAgcHJpbnQoCiAgICAgICAgcGxvdFBDQSgKICAgICAgICAgICAgcmVhZHNfc2NlLAogICAgICAgICAgICBjb2xvdXJfYnkgPSAiTGlicmFyeS5HZW5lcmF0aW9uLlNldCIsCiAgICAgICAgICAgIHNpemVfYnkgPSAidG90YWxfZmVhdHVyZXMiLAogICAgICAgICAgICBzaGFwZV9ieSA9ICJTb3VyY2UiLAogICAgICAgICAgICBleHByc192YWx1ZXMgPSBuCiAgICAgICAgKSArIGdndGl0bGUobikKICApCn0KYGBgCiMjIERldGVjdCBvdXRsaWVycwpgYGB7cn0KcmVhZHNfc2NlIDwtIHJ1blBDQShyZWFkc19zY2UsIHVzZV9jb2xkYXRhID0gVFJVRSwgZGV0ZWN0X291dGxpZXJzID0gVFJVRSkKb3V0bGllcnMgPC0gY29sbmFtZXMocmVhZHNfc2NlKVtyZWFkc19zY2Ukb3V0bGllcl0KaGVhZChvdXRsaWVycykKYGBgCiMjIEV4YW1pbmUgc291cmNlcyBvZiB2YXJpYW5jZSBhZnRlciByZW1vdmluZyB1bndhbnRlZCB2YXJpYXRpb24KYGBge3J9CmZvciAodmFyIGluIGMoInRvdGFsX2ZlYXR1cmVzIiwgIkFnZSIsICJMaWJyYXJ5LkdlbmVyYXRpb24uU2V0IiwgIkluZGV4IiwgIlBhcnRpY2lwYW50LklEIiwgIlNvdXJjZSIsICJSYWNlIiwgIkdlbmRlciIsICJDb2xsZWN0aW9uLkRhdGUiKSkgewogIHByaW50KAogICAgcGxvdFFDKHJlYWRzX3NjZSwgdHlwZSA9ICJmaW5kLXBjcyIsIGV4cHJzX3ZhbHVlcyA9ICJSVVZyIGs9IDIiLCB2YXJpYWJsZSA9IHZhcikKICAgICkgICsgZ2d0aXRsZSh2YXIpIAp9CmBgYAojIyBWaXN1YWxpemUgdG9wIGhpZ2hseS1leHByZXNzZWQgcGxhc21hLXZzLXNlcnVtIG1pUk5BcwpgYGB7cn0KbGlicmFyeShSQ29sb3JCcmV3ZXIpCmxpYnJhcnkocmVzaGFwZTIpCiMgUnlhbidzIGNvZGUsIG1vZGlmaWVkCm5vcm0uZXhwci5tYXRyIDwtIGV4cHJzKHJlYWRzX3NjZSkKIyBSYW5rIHRoZSBtZWFuIGV4cHJlc3Npb24gdmFsdWVzIGZvciBwbGFzbWEvc2VydW0gbWlScy4gSGlnaGVzdCBleHByZXNzaW9uID0gMQptZWFuLmV4cHIucGxhc21hLnJhbmsgPC0gcmFuaygtMSpyb3dNZWFucyhub3JtLmV4cHIubWF0clssIGNvbERhdGEocmVhZHNfc2NlKSRTb3VyY2U9PSJQbGFzbWEiXSkpCm1lYW4uZXhwci5zZXJ1bS5yYW5rIDwtIHJhbmsoLTEqcm93TWVhbnMobm9ybS5leHByLm1hdHJbLCBjb2xEYXRhKHJlYWRzX3NjZSkkU291cmNlPT0iU2VydW0iXSkpCnRvcF9OIDwtIDE4ICAjIHRvcF9OPTE4IHJlc3VscyBpbiAyMCBtaVJOQXMgaW4gdGhlIHBsb3QKdG9wLm1pUnMgPC0gcm93Lm5hbWVzKG5vcm0uZXhwci5tYXRyKVttZWFuLmV4cHIucGxhc21hLnJhbmsgPD0gdG9wX04gfCBtZWFuLmV4cHIuc2VydW0ucmFuayA8PSB0b3BfTl0gIyBnZXQgdGhlIG5hbWVzIG9mIHRoZSB0b3AgbWlScyBpbiBwbGFzbWEgb3Igc2VydW0Kbm9ybS5leHByLnRvcCA8LSBub3JtLmV4cHIubWF0clt0b3AubWlScywgXSAjIEdldCB0aGUgZXhwcmVzc2lvbiBtYXRyaXggZm9yIHRoZSB0b3AgbUlScwpub3JtLmV4cHIubWVsdCA8LSByZXNoYXBlMjo6bWVsdChub3JtLmV4cHIudG9wKSAjIENvbnZlcnQgeW91ciBub3JtYWxpemVkIGV4cHJlc3Npb24gbWF0cml4IHRvIGEgMyBjb2x1bW4gZGF0YS5mcmFtZSAocm93Lm5hbWUsIGNvbC5uYW1lLCBleHByZXNzaW9uIHZhbHVlKS4gTWVsdCBpcyBpbiB0aGUgZHB5bHIgcGFja2FnZSwgSSBiZWxpZXZlLgpjb2xuYW1lcyhub3JtLmV4cHIubWVsdCkgPC0gYygibWlSLklEIiwgIk1ULlVuaXF1ZS5JRCIsICJub3JtLmV4cHIiKSAjIGp1c3Qgc28gaXQncyBlYXNpZXIgZm9yIG1lIHRvIHRlbGwgeW91IHdoaWNoIGNvbHVtbnMgSSdtIHVzaW5nLgpub3JtLmV4cHIubWVsdCRTb3VyY2UgPC0gIiIKZm9yIChyb3dfbnVtIGluIDE6bnJvdyhub3JtLmV4cHIubWVsdCkpeyAgIyBQdWxsIHRoZSBwbGFzbWEvc2VydW0gc291cmNlIHZhbHVlIGZyb20gdGhlIGNvbHVtbiBtZXRhZGF0YS4KICBtdF91bmlxdWVfaWQgPC0gbm9ybS5leHByLm1lbHRbcm93X251bSwgXSRNVC5VbmlxdWUuSUQKICBub3JtLmV4cHIubWVsdFtyb3dfbnVtLCAiU291cmNlIl0gPC0gYXMuY2hhcmFjdGVyKHNhbXBsZXNbbXRfdW5pcXVlX2lkLCJTb3VyY2UiXSkKfQojIFRoaXMgd291bGQgYmUgYSBzaW1wbGUgYm94cGxvdCB3aXRoIHBsYXNtYS9zZXJ1bSBzaWRlLWJ5LXNpZGUuIE92ZXJsYXlpbmcgdGhlIGJveGVzIG92ZXIgcG9pbnRzIHRha2VzIGEgbGl0dGxlIG1vcmUgdHdlYWtpbmcgdG8gZ2V0IHRoZSBkb2RnZS93aWR0aCByaWdodCwgYnV0IGl0J3MgZG9hYmxlLgpnZ3Bsb3Qobm9ybS5leHByLm1lbHQsIGFlcyh4PXJlb3JkZXIobWlSLklELCBub3JtLmV4cHIsIEZVTj1tZWRpYW4pLCB5PW5vcm0uZXhwciwgZmlsbD1Tb3VyY2UpKSArIGdlb21fYm94cGxvdChwb3M9ImRvZGdlIiwgb3V0bGllci5zaXplPTAuNSkgKyAKICBnZ3RpdGxlKCJUb3AgMjAgRXhwcmVzc2VkIG1pUk5BcyIpICsgeWxhYigiTm9ybWFsaXplZCBFeHByZXNzaW9uIikgKyB4bGFiKCJtaVJOQSBJRCIpICsgCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvci54ID0gZWxlbWVudF9saW5lKHNpemUgPSAwLjUsIGxpbmV0eXBlID0gJ3NvbGlkJywgY29sb3VyID0gImdyZXkiKSwgCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSkpCmdncGxvdChub3JtLmV4cHIubWVsdCwgYWVzKHg9cmVvcmRlcihtaVIuSUQsIG5vcm0uZXhwciwgRlVOPW1lZGlhbiksIHk9bm9ybS5leHByLCBmaWxsPVNvdXJjZSkpICsgZ2VvbV9ib3hwbG90KHBvcz0iZG9kZ2UiLCBvdXRsaWVyLnNpemU9MC41KSArIAogIGdndGl0bGUoIlRvcCAyMCBFeHByZXNzZWQgbWlSTkFzIikgKyB5bGFiKCJOb3JtYWxpemVkIEV4cHJlc3Npb24iKSArIHhsYWIoIm1pUk5BIElEIikgKyAKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2xpbmUoc2l6ZSA9IDAuNSwgbGluZXR5cGUgPSAnc29saWQnLCBjb2xvdXIgPSAiZ3JleSIpLCAKICAgICAgICBwYW5lbC5ncmlkLm1ham9yLnggPSBlbGVtZW50X2JsYW5rKCkpICsgY29vcmRfZmxpcCgpCmBgYAoKIyBERSBhbmFseXNpcyB3aXRoIEVkZ2VSCmBgYHtyfQpsaWJyYXJ5KHBoZWF0bWFwKQojIGJhc2VkIG9uIFJ5YW4ncyBjb2RlLCBwYXNzIFJVVlNlcS1jb3JyZWN0ZWQgZGF0YSB0byBlZGdlUgpydXZyMiA8LSBydXZyX3NldHNbWzJdXQpkZXNpZ24gPC0gbW9kZWwubWF0cml4KH4gUGFydGljaXBhbnQuSUQgKyBTb3VyY2UgKyBXXzEgKyBXXzIsIHBEYXRhKHJ1dnIyKSkKZGdlIDwtIGVzdGltYXRlRGlzcChkZ2UsIGRlc2lnbiA9IGRlc2lnbiwgdGFnd2lzZSA9IFRSVUUsIHJvYnVzdCA9IFRSVUUpCmZpdCA8LSBnbG1GaXQoZGdlLCBkZXNpZ24pCiMgY29udHJhc3Qgc291cmNlIChwbGFzbWEgdnMgc2VydW0pCmxydCA8LSBnbG1MUlQoZml0LCBjb2VmPSJTb3VyY2VTZXJ1bSIpCnJlc3VsdHMgPC0gZGF0YS5mcmFtZSh0b3BUYWdzKGxydCwgbj1JbmYsIHNvcnQuYnk9IlBWYWx1ZSIsIHAudmFsdWU9MC4wNSkpCnNpZ19taVJzID0gbGlzdCgpCmZvciAobG9nRkNfdGhyZXNob2xkIGluIDE6MikgewogIHNpZ19taVJzW1tsb2dGQ190aHJlc2hvbGRdXSA8LSByb3duYW1lcyhyZXN1bHRzW3Jlc3VsdHMkUFZhbHVlIDwgMC4wNSAmIGFicyhyZXN1bHRzJGxvZ0ZDKSA+IGxvZ0ZDX3RocmVzaG9sZCxdKSAgIyBmaWx0ZXIgYnkgcC12YWx1ZSBhbmQgbG9nRkMKfQpzaWdfbWlSc1tbM11dIDwtIHJvd25hbWVzKHJlc3VsdHNbcmVzdWx0cyRQVmFsdWUgPCAwLjA1LF1bMDo1MCxdKQojIGhlYXRtYXBzIG9mIHNpZ25pZmljYW50IERFIG1pUk5BcwpTb3VyY2UgPC0gcERhdGEocnV2cjIpWyxjKCJTb3VyY2UiKV0KYW5ub3RhdGlvbnMgPC0gYXMuZGF0YS5mcmFtZShTb3VyY2UpCnJvd25hbWVzKGFubm90YXRpb25zKSA8LSByb3duYW1lcyhwRGF0YShydXZyMikpCmZvcihtaVJfbGlzdCBpbiBzaWdfbWlScykgewogIHBoZWF0bWFwKG5vcm0uZXhwci5tYXRyW21pUl9saXN0LF0sIGNsdXN0ZXJfcm93cz1UUlVFLCBzaG93X3Jvd25hbWVzPVRSVUUsIGNsdXN0ZXJfY29scz1UUlVFLCBhbm5vdGF0aW9uX2NvbD1hbm5vdGF0aW9ucywgbWFpbj0iRGlmZmVyZW50aWFsbHkgRXhwcmVzc2VkIG1pUk5BcyIpCn0KYGBgCiMgREUgQW5hbHlzaXMgd2l0aCBERVNlcTIKYGBge3J9CmxpYnJhcnkoREVTZXEyKSAgIyBUT0RPOiB1c2UgZmlsdGVyZWQgJiBSVVZTZXEtY29ycmVjdGVkIGRhdGEKZGVzZXEyRGF0YSA8LSBERVNlcURhdGFTZXRGcm9tTWF0cml4KGNvdW50RGF0YSA9IGRnZSRjb3VudHMsIGRlc2lnbiA9IH4gUGFydGljaXBhbnQuSUQgKyBTb3VyY2UsIGNvbERhdGEgPSBkZ2Ukc2FtcGxlcykKZGVzZXEyRGF0YSA8LSBERVNlcShkZXNlcTJEYXRhKQpyZXN1bHRzIDwtIHJlc3VsdHMoZGVzZXEyRGF0YSwgY29va3NDdXRvZmY9RkFMU0UsIGluZGVwZW5kZW50RmlsdGVyaW5nPUZBTFNFKQpoZWFkKGFzLmRhdGEuZnJhbWUocmVzdWx0cykpCmBgYAoKIyMgQ3JlYXRlIGEgaGVhdG1hcCBvZiB0aGUgdmFyaWFuY2Utc3RhYmlsaXppbmctdHJhbnNmb3JtZWQgZXhwcmVzc2lvbiBkYXRhCmBgYHtyfQogICAjIFRPRE86IG9ubHkgdXNlIHRvcCBERSBtaVJOQXMKdnN0RGF0YSA8LSB2YXJpYW5jZVN0YWJpbGl6aW5nVHJhbnNmb3JtYXRpb24oZGVzZXEyRGF0YSwgYmxpbmQ9RkFMU0UpICAjIHRyYW5zZm9ybSB0aGUgZGF0YQpzZWxlY3QgPC0gb3JkZXIocm93TWVhbnMoY291bnRzKGRlc2VxMkRhdGEsbm9ybWFsaXplZD1UUlVFKSksIGRlY3JlYXNpbmc9VFJVRSkKU291cmNlIDwtIGNvbERhdGEoZGVzZXEyRGF0YSlbLGMoIlNvdXJjZSIpXQphbm5vdGF0aW9ucyA8LSBhcy5kYXRhLmZyYW1lKFNvdXJjZSkKcm93bmFtZXMoYW5ub3RhdGlvbnMpIDwtIGNvbG5hbWVzKGRlc2VxMkRhdGEpCnBoZWF0bWFwKGFzc2F5KHZzdERhdGEpW3NlbGVjdCxdLCBjbHVzdGVyX3Jvd3M9VFJVRSwgc2hvd19yb3duYW1lcz1GQUxTRSwgY2x1c3Rlcl9jb2xzPVRSVUUsIGFubm90YXRpb25fY29sPWFubm90YXRpb25zKQpgYGAKCiMjIENyZWF0ZSBhIGhlYXRtYXAgb2Ygc2FtcGxlLXRvLXNhbXBsZSBkaXN0YW5jZXMKYGBge3J9CnNhbXBsZURpc3RzIDwtIGRpc3QodChhc3NheSh2c3REYXRhKSkpICAjIGNvbXB1dGUgc2FtcGxlLXRvLXNhbXBsZSBkaXN0YW5jZXMKc2FtcGxlRGlzdE1hdHJpeCA8LSBhcy5tYXRyaXgoc2FtcGxlRGlzdHMpCnJvd25hbWVzKHNhbXBsZURpc3RNYXRyaXgpIDwtIHBhc3RlKHZzdERhdGEkUGFydGljaXBhbnQuSUQsIHZzdERhdGEkU291cmNlLCBzZXA9IiAtICIpCmNvbG5hbWVzKHNhbXBsZURpc3RNYXRyaXgpIDwtIE5VTEwKY29sb3JzIDwtIGNvbG9yUmFtcFBhbGV0dGUoIHJldihicmV3ZXIucGFsKDksICJCbHVlcyIpKSApKDI1NSkKcGhlYXRtYXAoc2FtcGxlRGlzdE1hdHJpeCwKICAgICAgICAgY2x1c3RlcmluZ19kaXN0YW5jZV9yb3dzPXNhbXBsZURpc3RzLAogICAgICAgICBjbHVzdGVyaW5nX2Rpc3RhbmNlX2NvbHM9c2FtcGxlRGlzdHMsCiAgICAgICAgIGNvbD1jb2xvcnMpCmBgYAoKIyMgQ3JlYXRlIGEgUENBIHBsb3Qgc2hvd2luZyBBZ2UgeCBHZW5kZXIKYGBge3J9CnBsb3RQQ0EoCiAgICAgICAgICAgIHJlYWRzX3NjZSwKICAgICAgICAgICAgY29sb3VyX2J5ID0gIkFnZSIsCiAgICAgICAgICAgIHNoYXBlX2J5ID0gIkdlbmRlciIsCiAgICAgICAgICAgIHNpemVfYnkgPSAidG90YWxfZmVhdHVyZXMiLAogICAgICAgICAgICBleHByc192YWx1ZXMgPSAiUlVWciBrPSAyIgopICsgZ2d0aXRsZSgiUlVWU2VxLU5vcm1hbGl6ZWQgRXhwcmVzc2lvbiAoaz0yKSIpIApgYGAKYGBge3J9CnNhdmUuaW1hZ2UoImRpZmZfZXhwcl9wbGFzbWFfdnNfc2VydW0uUkRhdGEiKQpgYGAKCg==